<?php

namespace Sdk\lib;

use Common;
use Crypt_HMAC;
use Exception;
use HTTP_Request;
use PclZip;
use Shell;

class Core {
	const CoreClass = 'Lib\Core\Core';
	const UploadLimit = 100 * 1024;
	const SignExpire = 600;

	const Type_StaticMethod = 0;
	const Type_NewClass = 1;
	const Type_ObjMethod = 2;
	const Type_ApisMethod = 3;

	public static function RequestWit($class, $func, $param, $type = self::Type_StaticMethod) {
		$baseConf = Config::GetRoot('base');
		if (empty($baseConf['witUid'])) {
			throw new Exception('witUid is not exists, check conf/config.ini');
		}
		if (empty($baseConf['witPid'])) {
			throw new Exception('witPid is not exists, check conf/config.ini');
		}
		if (empty($baseConf['witSecretId'])) {
			throw new Exception('witSecretId is not exists, check conf/config.ini');
		}
		if (empty($baseConf['witSecretKey'])) {
			throw new Exception('witSecretKey is not exists, check conf/config.ini');
		}

		$requestBody = array(
			'witUid' => $baseConf['witUid'],
			'witPid' => $baseConf['witPid'],
			'witPluginId' => defined('PLUGIN_ID') ? PLUGIN_ID : '',
			'class' => $class,
			'func' => $func,
			'param' => $param,
			'type' => $type,
		);
		$blobData = !empty($_FILES) ? self::_packFiles() : '';
		$requestBody['t'] = time();
		$requestBody['sign'] = self::_getSign($baseConf['witSecretId'], $baseConf['witSecretKey'], $requestBody);
		$http = new HTTP_Request();
		$http->setURL(WitApiURL . '/lib');
		$http->setMethod('POST');
		$http->setBody($blobData . json_encode($requestBody));
		$response = $http->send();
		if ($response->getStatus() != 200) {
			if ($response->getStatus() == 302) {
				header('location: ' . $response->getHeader('location'));
				exit;
			} else {
				throw new Exception($func . '() response error status=' . $response->getStatus());
			}
		}
		$responseBody = json_decode($response->getBody(), true);
		if (!$responseBody) {
			throw new Exception($func . '() response error');
		}
		if ($responseBody['errCode'] || $responseBody['message']) {
			throw new Exception($func . '() response: ' . $responseBody['message'], $responseBody['errCode']);
		}
		return $responseBody['data'];
	}

	private static function _packFiles() {
		if (empty($_ENV['FILES'])) {
			return '';
		}
		$blobSplit = '/BLOB:' . Common::random(10) . '/';
		$blobData = '';
		foreach ($_ENV['FILES'] as $file) {
			$blobData .= $blobSplit . chr(0x1) . $file . $blobSplit . file_get_contents($file);
			@unlink($file);
		}
		if ($blobData !== '') {
			$blobData .= $blobSplit;
		}
		return $blobData;
	}

	private static function _getSign($witSecretId, $witSecretKey, $data) {
		$srcStr = $witSecretId . '|' . serialize($data);
		return Crypt_HMAC::encode($witSecretKey, 'sha1', $srcStr);
	}

	public static function Check() {
		try {
			$t = time();
			$ret = self::RequestWit(self::CoreClass, __FUNCTION__, array(
				't' => $t,
			));
		} catch (Exception $e) {
			throw new Exception($e->getMessage(), $e->getCode());
		}
		if (!$ret || $ret != $t) {
			throw new Exception('connect check is error');
		}
	}

	public static function GetDirs($plugin, $identifier) {
		$plugin = ucfirst($plugin);
		$path = ROOT_PATH . '/www/' . $plugin . '/' . $identifier;
		if (!file_exists($path)) {
			throw new Exception('plugin path is error');
		}
		$dirs = $return = array();
		self::getDir($path, $dirs);
		foreach ($dirs as $k => $v) {
			$return[str_replace($path, '', $k)] = $v;
		}
		return $return;
	}

	private static function getDir($curDir, &$dirs) {
		if ($dh = opendir($curDir)) {
			while (($file = readdir($dh)) !== false) {
				$f = $curDir . '/' . $file;
				if (in_array($file, array('.', '..')) || !is_dir($f)) {
					continue;
				}
				$size = 0;
				self::statSize($f, $size);
				$dirs[$f] = $size;
			}
			closedir($dh);
		}
		return $dirs;
	}

	private static function statSize($curDir, &$size) {
		if ($dh = opendir($curDir)) {
			while (($file = readdir($dh)) !== false) {
				$f = $curDir . '/' . $file;
				$size += filesize($f);
				if (in_array($file, array('.', '..')) || !is_dir($f)) {
					continue;
				}
				self::statSize($f, $size);
			}
			closedir($dh);
		}
		return $size;
	}

	public static function ReviewByGit($plugin, $identifier) {
		try {
			self::RequestWit(self::CoreClass, 'UploadPlugin',
				array(
					'plugin' => $plugin,
					'identifier' => $identifier,
					'step' => 'git',
					'data' => array(),
				));
		} catch (Exception $e) {
			throw new Exception($e->getMessage(), $e->getCode());
		}
	}

	public static function UploadPlugin($plugin, $identifier, $dirs = array()) {
		$plugin = ucfirst($plugin);
		$path = ROOT_PATH . '/www/' . $plugin . '/' . $identifier;
		if (!file_exists($path)) {
			throw new Exception('plugin path is error');
		}
		if (!Format::checkAppId($plugin) || !Format::checkBranchId($identifier)) {
			throw new Exception('plugin path format is error');
		}
		$f = tempnam(sys_get_temp_dir(), 'wit_');
		$zip = new PclZip($f);
		$removePath = dirname($path);
		if ($dirs) {
			foreach ($dirs as $dir) {
				$zip->add($path . $dir, PCLZIP_OPT_REMOVE_PATH, $removePath);
			}
		} else {
			$zip->create($path, PCLZIP_OPT_REMOVE_PATH, $removePath);
		}
		if (!file_exists($f)) {
			throw new Exception('plugin pack fail');
		}
		$size = filesize($f);
		if (!$size) {
			throw new Exception('plugin pack fail');
		}
		$md5 = md5_file($f);
		$fd = fopen($f, 'r');
		if (!$fd) {
			throw new Exception('plugin pack fail');
		}

		try {
			self::RequestWit(self::CoreClass, __FUNCTION__,
				array(
					'plugin' => $plugin,
					'identifier' => $identifier,
					'step' => 'start',
					'data' => array(
						'size' => $size,
						'md5' => $md5,
					)
				));
		} catch (Exception $e) {
			throw new Exception($e->getMessage(), $e->getCode());
		}

		$offset = 0;
		$step = 1;
		do {
			fseek($fd, $offset);
			$data = fread($fd, self::UploadLimit);
			try {
				self::RequestWit(self::CoreClass, __FUNCTION__,
					array(
						'plugin' => $plugin,
						'identifier' => $identifier,
						'step' => $step,
						'data' => base64_encode($data),
					));
			} catch (Exception $e) {
				throw new Exception($e->getMessage(), $e->getCode());
			}
			$offset += self::UploadLimit;
			$step++;
		} while ($offset < $size);
		@fclose($fd);

		try {
			self::RequestWit(self::CoreClass, __FUNCTION__,
				array(
					'plugin' => $plugin,
					'identifier' => $identifier,
					'step' => 'end',
					'data' => '',
				));
		} catch (Exception $e) {
			throw new Exception($e->getMessage(), $e->getCode());
		}
	}

	public static function GetInfo($plugins) {
		try {
			$return = self::RequestWit(self::CoreClass, __FUNCTION__,
				array(
					$plugins,
				));
		} catch (Exception $e) {
			return array();
		}
		return $return;
	}

	public static function SettingSet() {
		$post = self::GetPostData();


		$settingValue = ROOT_PATH . '/www/' . $post['plugin'] . '/' . $post['identifier'] . '/conf/setting.php';
		file_put_contents($settingValue, "<?php\n \$settingValue = " . var_export($post['setting'], true) . ';');
	}

	public static function SettingGet() {
		$post = self::GetPostData();

		if (!Format::checkAppId($post['plugin']) || !Format::checkBranchId($post['identifier'])) {
			return '';
		}

		$setting = ROOT_PATH . '/www/' . $post['plugin'] . '/' . $post['identifier'] . '/conf/setting.yaml';
		if (!file_exists($setting)) {
			return '';
		}

		return file_get_contents($setting);
	}

	public static function SettingGetValue() {
		$post = self::GetPostData();

		if (!Format::checkAppId($post['plugin']) || !Format::checkBranchId($post['identifier'])) {
			return '';
		}

		$setting = ROOT_PATH . '/plugin/' . $post['plugin'] . '/' . $post['identifier'] . '/conf/setting.php';
		if (!file_exists($setting)) {
			return array();
		}

		$settingValue = array();
		include $setting;
		return $settingValue;
	}

	public static function CommandGet() {
		$post = self::GetPostData();

		if (!Format::checkAppId($post['plugin']) || !Format::checkBranchId($post['identifier'])) {
			return '';
		}

		$setting = ROOT_PATH . '/www/' . $post['plugin'] . '/' . $post['identifier'] . '/conf/command.yaml';
		if (!file_exists($setting)) {
			return '';
		}

		return file_get_contents($setting);
	}

	public static function CommandGetValue() {
		$post = self::GetPostData();

		if (!Format::checkAppId($post['plugin']) || !Format::checkBranchId($post['identifier'])) {
			return '';
		}

		$setting = ROOT_PATH . '/plugin/' . $post['plugin'] . '/' . $post['identifier'] . '/conf/setting.php';
		if (!file_exists($setting)) {
			return array();
		}

		$settingValue = array();
		include $setting;
		return $settingValue;
	}

	public static function ReceivePlugin() {
		$post = self::GetPostData();

		$dir = sys_get_temp_dir() . '/wit_' . $post['file'];
		@mkdir($dir, 0777);
		$f = $dir . '/' . $post['file'];
		if ($post['step'] == 'start') {
			$f .= '.i';
			if (!file_put_contents($f, json_encode($post['data']))) {
				throw new Exception('receive error', 1003);
			}
			@unlink($f . '.zip');
		} elseif ($post['step'] == 'end') {
			$d = @file_get_contents($f . '.i');
			if (strlen($d) == 0) {
				throw new Exception('receive error', 1003);
			}
			@unlink($f . '.i');
			$data = json_decode($d, 1);
			$fd = fopen($f . '.zip', 'w');
			$step = 1;
			do {
				$d = @file_get_contents($f . '.' . $step);
				if (strlen($d) == 0) {
					break;
				}
				if (!fwrite($fd, $d)) {
					throw new Exception('receive error', 1003);
				}
				unlink($f . '.' . $step);
				$step++;
			} while (true);
			$md5 = md5_file($f . '.zip');
			$size = filesize($f . '.zip');
			if ($data['size'] != $size || $data['md5'] != $md5) {
				throw new Exception('receive error', 1003);
			}
			@fclose($fd);

			$zip = new PclZip($f . '.zip');
			$path = ROOT_PATH . '/www/' . $post['plugin'];
			Shell::mkDir($path);
			if (!$zip->extract(PCLZIP_OPT_PATH, $path)) {
				throw new Exception('receive error', 1003);
			}
			@unlink($f . '.zip');
		} else {
			$f .= '.' . $post['step'];
			if (!file_put_contents($f, base64_decode($post['data']))) {
				throw new Exception('receive error', 1003);
			}
		}
	}

	/**
	 * 检测签名返回包体
	 * @return array
	 * @throws Exception
	 */
	public static function GetPostData() {
		$postBody = file_get_contents('php://input');
		$data = json_decode($postBody, true);
		$baseConf = Config::GetRoot('base');
		if (empty($baseConf['witUid'])) {
			throw new Exception('witUid is not exists, check conf/config.ini', 1001);
		}
		if (empty($baseConf['witPid'])) {
			throw new Exception('witPid is not exists, check conf/config.ini', 1001);
		}
		if (empty($baseConf['witSecretId'])) {
			throw new Exception('witSecretId is not exists, check conf/config.ini', 1001);
		}
		if (empty($baseConf['witSecretKey'])) {
			throw new Exception('witSecretKey is not exists, check conf/config.ini', 1001);
		}

		if (!isset($data['witUid']) || $data['witUid'] != $baseConf['witUid']) {
			throw new Exception('witUid is invalid', 1002);
		}
		if (time() - $data['t'] > self::SignExpire) {
			throw new Exception('sign is invalid', 1002);
		}
		$sign = $data['sign'];
		unset($data['sign']);
		$srcStr = $baseConf['witSecretId'] . '|' . serialize($data);

		$checkSign = Crypt_HMAC::encode($baseConf['witSecretKey'], 'sha1', $srcStr);
		if ($checkSign != $sign) {
			throw new Exception('sign is invalid', 1002);
		}
		return $data;
	}

	public static function ParseSetting($data) {
		try {
			return self::RequestWit(self::CoreClass, __FUNCTION__,
				array(
					'data' => $data
				));
		} catch (Exception $e) {
			throw new Exception($e->getMessage(), $e->getCode());
		}
	}

}